/* 算法实现模板 */
import AlgorithmBase from "./AlgorithmBase";
import { mdRender} from "../../../common/Common";

import { showMathStringInTable } from "../../../visual/Table2D";
import 'katex/dist/katex.min.css';
import katex from 'katex';
class NBModel extends AlgorithmBase{
  constructor(){
    super('NBModel');
    this.descriptionUrl = `${this.homepage}/data/slm/ch4/README.md`;
    this.datasetUrl     = `${this.homepage}/data/slm/ch4/dataset.json`;
    this.model = {lambda:0.2};

    // this.trainLog = [];
    // this.len = 0
    // this.Ct = 0 
    // this.max = 0
    // this.flag = 0
    // this.node_name_ = ''
    // this.cmd =''
    // this.width = 0
  }

  init(){ 
    this.reset();
    this.initTrainDS();
  }
  
  reset(){
    this.dataset = undefined;
    this.trainDS = undefined;
    this.model          = {lambda:0.2};
    this.clear();
    this.initDescription();
    this.updateModel();
  }

  loadModel(json){
    if(json.TrainDS){
      if(json.TrainDS instanceof Array){
        this.trainDSList = [];
        for(let i = 0, il = json.TrainDS.length; i < il; i++){
          const dat = json.TrainDS[i];
          this.trainDSList.push(dat);
        }
        this.trainDS = this.trainDSList[0];
      }
      else{
        let trainDS = json.TrainDS;
        this.trainDS = trainDS;
      }
    }
    return this.trainDS

  }
  
  // 模型UI
  initModelUI(){
    this.addLabel(this.domElementList.model, '\lambda:');
    this.lambdaInput = this.addFloatInput(
      this.domElementList.model, this.model.lambda, 
      (event)=>{
        const val = parseFloat(event.target.value);
        this.model.lambda = val;
        this.updateModel();
      });
    this.modelDiv = this.addDiv(this.domElementList.model);
  }


  // 渲染模型到页面
  updateModel(x, y){
    // 显示数据集
    if(this.trainDS){
      if(x === undefined) x = this.trainDS.x;
      if(y === undefined) y = this.trainDS.y;
      var string = String.raw`T=\{`;
      for(var i=0,il=x.length;i<il;i++){
        string += String.raw`((${x[i].slice(0,2)})^T,`;
        string += String.raw `${y[i]})`;
        if(i!==il-1){
          string += ', ';
        }
      }
      string += String.raw`\}`;
      katex.render(string, this.inputDiv, { throwOnError: false });
      // mdRender(`${string}`, this.inputDiv);      
    }
  }

  // 输入数据UI
  initInputUI(){

    // 文件选择器
    const fileSelector      = document.createElement('input');
    fileSelector.type       = 'file';
    fileSelector.hidden     = true;
    fileSelector.onchange   = this.handleFileChange.bind(this);   
    
    this.addButton(this.domElementList.input, "获取服务器数据", this.initTrainDS.bind(this));
    this.addButton(this.domElementList.input, "使用本地数据", this.openFileSelector.bind(this));
    this.domElementList.input.appendChild(fileSelector);
    this.addButton(this.domElementList.input, "计算", this.calculate.bind(this));
    this.addButton(this.domElementList.input,"例子",this.example.bind(this));
    // this.playButton = this.addButton(this.domElementList.input, "训练动画", this.trainAnimation.bind(this));
    // this.playButton.hidden = true;

    this.labelTrainTips = this.addLabel(this.domElementList.input, '');
    this.inputDiv       = this.addDiv(this.domElementList.input);

    this.fileSelector  = fileSelector;
  }

  // 输出UI
  initOutputUI(){
    this.outputDiv   = this.addDiv(this.domElementList.output);
    this.trainLogDiv = this.addDiv(this.domElementList.output);
  }

  // 获取描述md文件
  initDescription(){
    fetch(this.descriptionUrl).then((response)=>{
      return response.text();
    }).then((txt)=>{

      this.reandMD(txt, this.descriptionDiv)
    })
  }

  // 获取服务器数据集
  initTrainDS(){
    this.prepare(this.datasetUrl);
  }

  // 准备数据训练数据
  prepare(url){
    fetch(url).then((response)=>{
      return response.json();
    }).then((json)=>{
      this.trainDS = this.loadModel(json);
      this.updateModel();
    })
  }

  openFileSelector(){
    this.fileSelector.click();
  }
  // 获取文件路径加载
  handleFileChange(event){
    const files = event.target.files;

    for(let i = 0, il = files.length; i < il; i++){
      let reader = new FileReader();
      reader.onload = (e) => {      
        this.prepare(e.target.result);
        if(i === files.length - 1){
          event.target.value = '';
        }
      };
      reader.readAsDataURL(files[i])
    }
  }
  
  set_fun(matrix){
    var set_matrix = [];
    for(var i=0,il=matrix.length;i<il;i++){
      if(set_matrix.indexOf(matrix[i])===-1){
        set_matrix.push(matrix[i]);
      }
    }
    return set_matrix;
  }//返回无重复元素的列表

  slice_fun(matrix,col){
    var col_matrix=[];
    for(var i = 0,il= matrix.length; i<il; i++){
      col_matrix.push(matrix[i][col]);
    }
    return col_matrix;
  }//col：列，返回matrix中的第col列

  getRepeatNum(arr){ 
    var obj = {}; 
    for(var i= 0, l = arr.length; i< l; i++){ 
        var item = arr[i]; 
        obj[item] = (obj[item] +1 ) || 1; 
    } 
    return obj; 
  }//返回一个记数字典

  fit(lambda,x,y){
    var data = x;
    var N = x.length;
    var M = x[0].length;
    var py = {};
    var pxy = {};
    var uniquey = [];
    var tmp = {"-1":0,"1":0};
    var tmp_data = [];
    var uniquecol = [];
    var tmp_data_col = [];
    var tmp1 = {"-1":0,"1":0};
    var py_xy = [];

    for(var i=0,il=x.length;i<il;i++){
      data[i].push(y[i]);
    }

    uniquey = this.set_fun(y);
    tmp = this.getRepeatNum(y);

    for(var key in tmp){
      py[key]=(tmp[key]+this.model.lambda)/(N+uniquey.length*this.model.lambda);
      for(var i =0,il=data.length;i<il;i++){
        if(data[i][M]===parseInt(key) ){
          tmp_data.push(data[i]);
        }  
        
      }
      for(var col =0,coll=M;col<coll;col++){
        tmp_data_col =this.slice_fun(tmp_data,col);
        uniquecol = this.set_fun(tmp_data_col);
        tmp1 = this.getRepeatNum(tmp_data_col);
        for(var keys in tmp1){
          var col_ = col+1;
          pxy["X=("+col_+")="+keys+"|Y=("+key+")"] = (tmp1[keys]+this.model.lambda)/(tmp[key]+uniquecol.length*this.model.lambda);
          
        }
      }
      tmp_data = [];
    }
    py_xy[0] = py;
    py_xy[1] = pxy;
    return py_xy;
  }

  predict(input){
    const data = this.trainDS.x;
    const label = this.trainDS.y;
    var M = input.length;
    var res = {};
    var py_pxy = this.fit(0.2,data,label);
    var py = py_pxy[0];
    var pxy = py_pxy[1];
    for (var key in py){
      var p = py[key];
      for (var i=0,il=M;i<il;i++){
        var i_ = i+1;
        p = p*pxy["X=("+i_+")="+input[i]+"|Y=("+key+")"]
      }
      res[key] = p;
    }
    var maxp = -1;
    var maxk = -1;
    for (var keys in res){
      if (res[keys]>maxp){
        maxp = res[keys];
        maxk = keys;
      }
    }
    var out = [];
    out.push(res);
    out.push(maxk);
    return out;
    }
  
  creat_test_data(input){
    var test_data = [];
    for(var i=0,il=input.length;i<il;i++){
      test_data.push(input[i].slice(0,2));
    }
    return test_data;
  
  }

  // 训练
  calculate(){
    var testTable = [];
    testTable.push([String.raw`\textbf{ID}`,String.raw`\textbf{测试数据}`,String.raw`\textbf{分类为1的概率}`, String.raw`\textbf{分类为-1的概率}`,String.raw`\textbf{分类}`]);
    var test_data = this.creat_test_data(this.trainDS.x);
    for(var i =1,il=test_data.length;i<=il;i++){
      testTable.push([]);
      testTable[i][0] = String.raw`${i}`;
      testTable[i][1] = String.raw`(${test_data[i-1]})^T`;
      testTable[i][2] = String.raw`${this.predict(test_data[i-1])[0][1].toFixed(4)}`;
      testTable[i][3] = String.raw`${this.predict(test_data[i-1])[0][-1].toFixed(4)}`;
      testTable[i][4] = String.raw`${this.predict(test_data[i-1])[1]}`;
    }
    if(this.trainLogDiv) this.trainLogDiv.innerHTML = '';
    showMathStringInTable(this.trainLogDiv, testTable);

  }
  example(){
    if(this.trainLogDiv) this.trainLogDiv.innerHTML = '';
    var string1 = String.raw`请点击服务器数据,以(2,S)^T为例,按算法描述依次计算:\\(1)  P(Y=i)\qquad i =1,-1\\  P(x^{(1)}=i|Y=k) \qquad i=1,2,3\quad k=1,-1\\P(x^{(2)}=i|Y=k) \qquad i=S,L,M\quad k=1,-1\\(2)计算P(Y=1)P(x^{(1)}=2|Y=1)P(x^{(2)}=S|Y=1)以及P(Y=-1)P(x^{(1)}=2|Y=-1)P(x^{(2)}=S|Y=-1)\\(3)比较两者大小,结果取数值大者。`;
    katex.render(string1, this.trainLogDiv, { throwOnError: false });    
  }

}


export default NBModel;

// const modelString = String.raw
// `$$ \begin{aligned} \lambda&=0.2\end{aligned} $$`
// mdRender(`${modelString}`, this.inputDiv);
